package com.bzkj.framework.aspectj;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.bzkj.common.annotation.LogD;
import com.bzkj.common.core.domain.AjaxResult;
import com.bzkj.common.core.domain.model.LoginUser;
import com.bzkj.common.enums.BusinessStatus;
import com.bzkj.common.enums.BusinessType;
import com.bzkj.common.enums.HttpMethod;
import com.bzkj.common.filter.PropertyPreExcludeFilter;
import com.bzkj.common.utils.SecurityUtils;
import com.bzkj.common.utils.ServletUtils;
import com.bzkj.common.utils.StringUtils;
import com.bzkj.common.utils.ip.AddressUtils;
import com.bzkj.common.utils.ip.IpUtils;
import com.bzkj.framework.manager.AsyncManager;
import com.bzkj.framework.manager.factory.AsyncFactory;
import com.bzkj.system.domain.SysOperOrderLog;
import com.bzkj.system.service.ISysOperOrderLogService;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName LogAspectD
 * @Description TODO 融合系统日志切面
 * @Author C1309
 * @Date 2025/5/20 11:44
 */
@Aspect
@Component
public class LogAspectD {
    private static final Logger log = LoggerFactory.getLogger(LogAspectD.class);

    @Resource
    private ISysOperOrderLogService operLogService;

    /**
     * 排除敏感属性字段
     */
    public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    @Around(value = "@annotation(logD)", argNames = "pjp,logD")
    public Object handleLog(ProceedingJoinPoint pjp, LogD logD) throws Throwable {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
        // 用于存储被代理方法的执行结果
        Object result;
        SysOperOrderLog operLog = null;
        try {
            // 获取当前的用户
            LoginUser loginUser = SecurityUtils.getLoginUser();

            // 创建操作日志对象
            operLog = createOperLog(pjp, loginUser, logD);

            // 设置方法名称
            setMethodInfo(pjp, operLog);

            // 处理设置注解上的参数
            result = getControllerMethodDescription(pjp, logD, operLog, null);

        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息: {}", exp.getMessage(), exp);
            if (operLog != null) {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(exp.getMessage());
                AsyncManager.me().execute(AsyncFactory.recordOrderOper(operLog));
            }
            throw exp; // 重新抛出异常，确保调用方能够捕获到异常
        } finally {
            TIME_THREADLOCAL.remove();
        }

        // 返回被代理方法的执行结果
        return result;
    }

    /**
     * 创建操作日志对象并设置基本信息
     */
    private SysOperOrderLog createOperLog(ProceedingJoinPoint joinPoint, LoginUser loginUser, LogD logD) {
        SysOperOrderLog operLog = new SysOperOrderLog();
        operLog.setStatus(BusinessStatus.SUCCESS.ordinal());

        // 请求的地址--ip
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        operLog.setOperIp(ip);

        // 请求的地址--url
        operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));

        // 操作人
        if (loginUser != null) {
            operLog.setOperName(loginUser.getUsername());
        }
        operLog.setOperLocation(AddressUtils.getRealAddressByIP(ip));

        // 设置action动作
        operLog.setBusinessType(logD.businessType().ordinal());
        // 设置标题
        operLog.setTitle(logD.title());
        // 设置操作人类别
        operLog.setOperatorType(logD.operatorType().ordinal());

        return operLog;
    }

    /**
     * 设置方法信息
     */
    private void setMethodInfo(ProceedingJoinPoint joinPoint, SysOperOrderLog operLog) {
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        operLog.setMethod(className + "." + methodName + "()");

        // 设置请求方式
        operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     */
    public Object getControllerMethodDescription(ProceedingJoinPoint joinPoint, LogD log, SysOperOrderLog operLog, Object jsonResult) throws Throwable {
        Object[] args = joinPoint.getArgs();
        String pkFieldName = log.pkFieldName();
        Object result;
        // 设置action动作
        if (log.businessType().ordinal() == BusinessType.UPDATE.ordinal()) {
            result = processUpdateLog(joinPoint, log, operLog, jsonResult);
        } else {
            result = joinPoint.proceed();
            AjaxResult ajaxResult = (AjaxResult) result;
            if (ajaxResult.isSuccess()) {
                if (log.businessType().ordinal() == BusinessType.IMPORT.ordinal()) {
                    operLog.setOperDesc(operLog.getOperName() + log.businessType().getDescription() + operLog.getTitle() + "。");
                } else if (log.businessType().ordinal() == BusinessType.INSERT.ordinal()) {
                    Object pkField = extractPrimaryKey(pkFieldName, args);
                    operLog.setOperDesc(operLog.getOperName() + log.businessType().getDescription() + operLog.getTitle() + "。" + "主键值：" + pkField);
                }

            } else {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(ajaxResult.get("msg").toString());
            }
            operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
            AsyncManager.me().execute(AsyncFactory.recordOrderOper(operLog));
        }
        return result;
    }

    /**
     * 处理请求
     *
     * @param joinPoint  切点
     * @param log        日志注解
     * @param operLog    操作日志
     * @param jsonResult 返回结果
     */
    public Object processUpdateLog(ProceedingJoinPoint joinPoint, LogD log, SysOperOrderLog operLog, Object jsonResult) throws Throwable {
        // 提取主要所在的表
        String tableName = log.tableNames();
        // 提取主要表主键
        String pkFieldNames = log.pkFieldNames();
        // 提取请求地址
        String url = log.request_url();

        // 获取方法参数，解析主键 ID
        Object[] args = joinPoint.getArgs();
        Object primaryKey = extractPrimaryKey(pkFieldNames, args);

        Map<String, Object> oldInfo = Collections.emptyMap();
        // 获取旧数据
        long sqlStart = System.currentTimeMillis();
        if (url.split("\\{").length == 1) {
            oldInfo = operLogService.selectOldInfoByPk(pkFieldNames, url, primaryKey);
        } else if (url.split("\\{").length == 2) {
            oldInfo = operLogService.selectGetRestFulApi(primaryKey, url);
        }
        long sqlEnd = System.currentTimeMillis();

        // 执行原方法（更新数据）
        Object result = joinPoint.proceed();
        // 计算耗时
        operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get() - (sqlEnd - sqlStart));
        Map<String, Object> newInfo = Collections.emptyMap();
        // 查询新数据
        if (url.split("\\{").length == 1) {
            newInfo = operLogService.selectOldInfoByPk(pkFieldNames, url, primaryKey);
        } else if (url.split("\\{").length == 2) {
            newInfo = operLogService.selectGetRestFulApi(primaryKey, url);
        }
        // 获取字段翻译集
        Map<String, String> fieldTranslationMap = operLogService.getFieldTranslations(tableName)
                .stream()
                .collect(Collectors.toMap(m -> m.get("field_name"), m -> m.get("field_cn_name")));

        // 比较并翻译字段
        Map<String, Map<String, String>> translateChanges = translateChanges(fieldTranslationMap, oldInfo, newInfo);
        operLog.setOperDesc(operLog.getOperName() + log.businessType().getDescription() + operLog.getTitle() + "。");
        operLog.setDetailType("header");
        operLog.setOperationDetail(JSONObject.toJSONString(translateChanges));
        // 存储操作记录
        operLogService.insertOperlog(operLog);
        return result;
    }

    private Object extractPrimaryKey(String idField, Object[] args) {
        for (Object arg : args) {
            Field[] declaredFields = arg.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                if (field.getName().equals(idField)) {
                    try {
                        field.setAccessible(true);
                        return field.get(arg);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return null;
    }

    /**
     * 生成修改日志，翻译字段
     */
    private Map<String, Map<String, String>> translateChanges(Map<String, String> fieldTranslationMap,
                                                              Map<String, Object> oldData, Map<String, Object> newData) {
        Map<String, Map<String, String>> changes = new HashMap<>();
        for (String key : newData.keySet()) {
            if (oldData != null && oldData.containsKey(key) && !Objects.equals(oldData.get(key), newData.get(key))) {
                String fieldDesc = fieldTranslationMap.get(key);
                if (fieldDesc != null) {
                    HashMap<String, String> detailMap = new HashMap<>();
                    detailMap.put("oldValue", oldData.get(key).toString());
                    detailMap.put("newValue", newData.get(key).toString());
                    changes.put(fieldDesc, detailMap);
                }
            }
        }
        return changes;
    }


    /**
     * 获取请求的参数，放到log中
     *
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperOrderLog operLog, String[] excludeParamNames) throws Exception {
        Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
        String requestMethod = operLog.getRequestMethod();
        if (StringUtils.isEmpty(paramsMap)
                && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) {
            String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
        } else {
            operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
                    try {
                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
                        params.append(jsonObj).append(" ");
                    } catch (Exception e) {
                    }
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 忽略敏感属性
     */
    public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) {
        return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }


}